// test file of sample.json data from "https://dataportals.org/api/data.json"
// Author: dongjinguang
// Time: 2023-09-01
#include "cJSON.h"
#include "nlohmann/json.hpp"
#include "json/json.h"
#include <chrono>
#include <fstream>
#include <iostream>
#include <string>
#include <unistd.h>

using namespace std;
using json = nlohmann::json;

string readJSONFile(const string &filename) {
  ifstream file(filename);
  if (!file.is_open()) {
    cerr << "Error opening file: " << filename << endl;
    exit(EXIT_FAILURE);
  }
  string content((istreambuf_iterator<char>(file)),
                 (istreambuf_iterator<char>()));
  return content;
}

size_t getMemoryUsage() {
  string cmd = "ps -o rss= -p " + to_string(getpid());
  FILE *pFile = popen(cmd.c_str(), "r");
  if (!pFile) {
    cerr << "Error executing command: " << cmd << endl;
    exit(EXIT_FAILURE);
  }
  size_t memory;
  fscanf(pFile, "%zd", &memory);
  pclose(pFile);
  return memory * 1024; // Convert from KB to bytes
}

// Function to generate a large JSON data structure
json generateLargeData() {
  json data;
  for (int i = 0; i < 10000; ++i) {
    data[to_string(i)] = "This is a large piece of data.";
  }
  return data;
}

void performMemoryTest() {
  string jsonStr = readJSONFile("sample.json");

  // Measure initial memory usage
  size_t initial_memory = getMemoryUsage();

  // cjson parsing
  cJSON *cjson_root = cJSON_Parse(jsonStr.c_str());
  size_t cjson_memory_start = getMemoryUsage();

  // jsoncpp parsing
  Json::CharReaderBuilder jsoncpp_builder;
  Json::Value jsoncpp_root;
  istringstream jsoncpp_stream(jsonStr);
  Json::parseFromStream(jsoncpp_builder, jsoncpp_stream, &jsoncpp_root,
                        nullptr);
  size_t jsoncpp_memory_start = getMemoryUsage();

  // nlohmann/json parsing
  json nlohmann_root = json::parse(jsonStr);
  size_t nlohmann_memory_start = getMemoryUsage();

  // Calculate memory consumption
  size_t cjson_memory_consumed = cjson_memory_start - initial_memory;
  size_t jsoncpp_memory_consumed = jsoncpp_memory_start - cjson_memory_start;
  size_t nlohmann_memory_consumed =
      nlohmann_memory_start - jsoncpp_memory_start;

  cout << "cjson memory consumed: " << cjson_memory_consumed << " bytes"
       << endl;
  cout << "jsoncpp memory consumed: " << jsoncpp_memory_consumed << " bytes"
       << endl;
  cout << "nlohmann/json memory consumed: " << nlohmann_memory_consumed
       << " bytes" << endl;

  // Free cjson root
  cJSON_Delete(cjson_root);
}

void performPerformanceTest() {
  string jsonStr = readJSONFile("sample.json");

  // Using cjson
  auto cjson_start = chrono::high_resolution_clock::now();
  cJSON *cjson_root = cJSON_Parse(jsonStr.c_str());
  auto cjson_end = chrono::high_resolution_clock::now();
  auto cjson_duration =
      chrono::duration_cast<chrono::microseconds>(cjson_end - cjson_start);

  // Using jsoncpp
  Json::CharReaderBuilder jsoncpp_builder;
  Json::Value jsoncpp_root;
  istringstream jsoncpp_stream(jsonStr);
  auto jsoncpp_start = chrono::high_resolution_clock::now();
  Json::parseFromStream(jsoncpp_builder, jsoncpp_stream, &jsoncpp_root,
                        nullptr);
  auto jsoncpp_end = chrono::high_resolution_clock::now();
  auto jsoncpp_duration =
      chrono::duration_cast<chrono::microseconds>(jsoncpp_end - jsoncpp_start);

  // Using nlohmann/json
  auto json_start = chrono::high_resolution_clock::now();
  json nlohmann_root = json::parse(jsonStr);
  auto json_end = chrono::high_resolution_clock::now();
  auto json_duration =
      chrono::duration_cast<chrono::microseconds>(json_end - json_start);

  cout << "cjson execution time: " << cjson_duration.count() << " microseconds"
       << endl;
  cout << "jsoncpp execution time: " << jsoncpp_duration.count()
       << " microseconds" << endl;
  cout << "nlohmann/json execution time: " << json_duration.count()
       << " microseconds" << endl;
  cout << endl;
  // Free cjson root
  cJSON_free(cjson_root);
}

// Function to convert nlohmann/json to jsoncpp Json::Value
Json::Value convertToJsonCpp(const json &nlohmannData) {
  Json::Value jsoncppData;
  for (const auto &entry : nlohmannData.items()) {
    jsoncppData[entry.key()] = Json::Value(entry.value().get<string>());
  }
  return jsoncppData;
}

// Function to perform serialization and measure performance
void performSerializationTest(const json &data, const string &testName) {
  // Using cjson
  cJSON *cjson_data = cJSON_Parse(data.dump().c_str());
  auto cjson_start = chrono::high_resolution_clock::now();
  string cjson_str = cJSON_Print(cjson_data);
  auto cjson_end = chrono::high_resolution_clock::now();
  auto cjson_duration =
      chrono::duration_cast<chrono::microseconds>(cjson_end - cjson_start);
  cJSON_Delete(cjson_data);

  // Using jsoncpp
  Json::StreamWriterBuilder jsoncpp_builder;
  Json::Value jsoncpp_root = convertToJsonCpp(data);
  auto jsoncpp_start = chrono::high_resolution_clock::now();
  string jsoncpp_str = Json::writeString(jsoncpp_builder, jsoncpp_root);
  auto jsoncpp_end = chrono::high_resolution_clock::now();
  auto jsoncpp_duration =
      chrono::duration_cast<chrono::microseconds>(jsoncpp_end - jsoncpp_start);

  // Using nlohmann/json
  auto json_start = chrono::high_resolution_clock::now();
  string nlohmann_str = data.dump();
  auto json_end = chrono::high_resolution_clock::now();
  auto json_duration =
      chrono::duration_cast<chrono::microseconds>(json_end - json_start);

  // Output results
  cout << "Serialization Test (" << testName << "):" << endl;
  cout << "cjson execution time: " << cjson_duration.count() << " microseconds"
       << endl;
  cout << "jsoncpp execution time: " << jsoncpp_duration.count()
       << " microseconds" << endl;
  cout << "nlohmann/json execution time: " << json_duration.count()
       << " microseconds" << endl;
  cout << "Serialized JSON size (bytes): " << cjson_str.size() << " (cjson), "
       << jsoncpp_str.size() << " (jsoncpp), " << nlohmann_str.size()
       << " (nlohmann/json)" << endl;
}

int main() {
  cout << "Performing Memory Test:" << endl;
  performMemoryTest();

  cout << "\nPerforming Performance Test:" << endl;
  performPerformanceTest();

  // Generate a large JSON data structure
  json largeData = generateLargeData();

  // Perform serialization and measure performance
  performSerializationTest(largeData, "Large Data");
  return 0;
}